package com.baidu.leetcode.datastructures.stack;

/**
 * @author shilei
 * @create 2021-11-09 19:22
 * 使用栈模拟实现一个计算器  ----- 中缀表达式
 * 1、通过一个 index 值（索引），来遍历我们的表达式
 * 2、如果我们发现是一个数字 就直接进入数栈
 * 3、如果发现扫描到的是一个符号 就分如下情况
 * 3.1、如果发现当前的符号栈为空，就直接入栈
 * 3.2、如果符号栈有操作符，就进行比较，如果当前的操作符的优先级小于或者等于栈中的操作符，就需要从数栈中 pop 出两个数，在从符号栈中 pop 出一个符号，进行运算。将得到结果，入数栈，然后将当前的操作符入符号栈，如果当前的操作符的优先级大于栈中的操作符，就直接入符号栈。
 * 4、当表达式扫描完毕，就顺序的从数栈和符号栈中pop 出相应的数和符号，并运行。
 * 5、最后在数栈只有一个数字，就是表达式的结果。
 */
public class Calculator {
    public static void main(String[] args) {
        //根据上面的思路 完成表达式的运算
        String expression = "7*2*2-5+1-5+3-4";
        //创建两个栈 数栈 一个符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        //定义需要的相关变量
        int index = 0;//用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int result = 0;
        char ch = ' ';//将每次扫描得到的char 保存到ch
        String keepNum = "";//用于拼接多位数  当表达式出现多位数的数字
        //开始while 循环的扫描 expression
        while (true) {
            //依次得到expression 的每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            //判断ch 是什么，然后做相应的处理
            if (operStack.isOper(ch)) {
                //如果是运算符 判断当前符号栈是否为空 进行添加
                if (!operStack.isEmpty()) {
                    //如果符号栈有操作符，就进行比较，如果当前的操作符的优先级小于或者等于栈中的操作符，
                    // 就需要从数栈中 pop 出两个数，在从符号栈中 pop 出一个符号，进行运算。
                    // 将得到结果，入数栈，然后将当前的操作符入符号栈，如果当前的操作符的优先级大于栈中的操作符，
                    // 就直接入符号栈。
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        //说明栈中的优先级大于当前符号 进行运算
                        num1 = numStack.popStack();
                        num2 = numStack.popStack();
                        oper = operStack.popStack();
                        result = numStack.cal(num1, num2, oper);
                        //将运算结束的结果入栈
                        numStack.pushStack(result);
                        //将当前符号入栈
                        operStack.pushStack(ch);
                    } else {
                        operStack.pushStack(ch);
                    }

                } else {
                    //为空 直接入栈
                    operStack.pushStack(ch);
                }
            } else {
                //如果是数 则直接入数栈 因为ascli 码对应的 char '1' 和 1 相差 48
//                numStack.pushStack(ch - 48);  如果当前数是多位数的情况下 会有问题
                //分析思路
                /*
                    1、当处理多位数时，不能发现是一个数就立即入栈，因为可能是多位数
                    2、咋处理数时，需要向 expression 的表达式的 index 后再看一位，如果是数进行扫描，如果是符号才入栈
                    3、因此我们需要一个字符串变量 用于拼接
                 */
                //处理多位数
                keepNum += ch;
                //如果当前已经是最后一位数字 则直接入栈
                if (index == expression.length() - 1) {
                    numStack.pushStack(Integer.parseInt(keepNum));
                } else {
                    //判断下一个字符是不是数字 如果是数字 就继续扫描 如果是运算符 就入栈
                    //注意是看后一位 不是index++
                    if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))) {
                        //如果后一位是运算符 则入栈当前数字
                        numStack.pushStack(Integer.parseInt(keepNum));
                        //重要 ！！！！ keepNum 入栈之后需要清空
                        keepNum = "";
                    }
                }
            }
            //让 index++ 并判断是否扫描到 expression 的最后
            index++;
            if (index >= expression.length()) {
                break;
            }
        }
        //当表达式扫描完毕，就顺序的从数栈和符号栈中pop 出相应的数和符号，并运行。
        while (true) {
            //如果符号栈为空 则计算到最后结果 数栈中只有一个数字
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.popStack();
            num2 = numStack.popStack();
            oper = operStack.popStack();
            result = numStack.cal(num1, num2, oper);
            numStack.pushStack(result);
        }
        System.out.printf("表达式 %s = %d", expression, numStack.popStack());
    }

}

//定义一个 ArrayStack 表示栈
class ArrayStack2 {
    private int maxSize;//表示栈的大小
    private int[] stack;//表示栈 存储数据
    private int top = -1;//表示栈顶 默认-1

    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    //返回当前栈顶的值
    public int peek() {
        return stack[top];
    }

    //判断栈满
    public boolean isFull() {
        return maxSize - 1 == top;
    }

    //判断栈空
    public boolean isEmpty() {
        return top == -1;
    }

    //入栈
    public void pushStack(int data) {
        //判断是否栈满
        if (isFull()) {
            return;
        }
        top++;
        stack[top] = data;
    }

    //出栈
    public int popStack() {
        //判断栈空
        if (isEmpty()) {
            throw new RuntimeException("栈空，没有数据~");
        }
        int empValue = stack[top];
        top--;
        return empValue;
    }

    //显示栈 需要先从栈顶显示数据
    public void forEachStack() {
        if (isEmpty()) {
            System.out.println("栈空");
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d] is:%d \n", i, stack[i]);
        }
    }

    //返回运算符的优先级 优先级是程序员来定 优先级使用数字表示
    //数字越大 则优先级越高
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1; //假设当前运算符只有 + - * /
        }
    }

    //判断是不是一个运算符
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    //计算方法
    public int cal(int num1, int num2, int oper) {
        int result = 0;
        switch (oper) {
            case '+':
                result = num2 + num1;
                break;
            case '-':
                result = num2 - num1; //注意需要后弹出来的数 - || / 先弹出来的数
                break;
            case '*':
                result = num2 * num1;
                break;
            case '/':
                result = num2 / num1;
                break;
        }
        return result;
    }
}